﻿<script setup lang="ts" name="UploadSource">
import { propTypes } from '@/utils/propTypes'
import { isString, isArray } from '@/utils/is'
import type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
import { useUpload } from './useUpload'
import { getAcceptSize, fileExtList, getMimetype, getFileExt, AcceptSizeType, getAcceptSizeByAccept, getExtMimetypeConfig } from '@/utils/file/helper'
import { MimetypeType } from '@/utils/file/type'
interface UploadResourceFile extends UploadUserFile {
    mimetype: string,
    ext: string | undefined,
    duration?: number
}

const message = useMessage() // 消息弹窗
const { getPrefixCls } = useDesign()

const prefixCls = getPrefixCls('upload-source')
const { t } = useI18n() // 国际化

const emit = defineEmits(['update:modelValue', 'success'])

const props = defineProps({
  modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
  size: propTypes.oneOfType([String, Number]).def('160px'),
  mimetype: {
    type: [String, Array] as import('vue').PropType<MimetypeType>,
    default: ''
  }, // 用于根据mimetype类型，支持上传默认支持的扩展名文件
  accept: propTypes.string.def(''), // 用于支持上传指定扩展名文件
  type: propTypes.oneOf(['upload', 'picker']).def('upload'), // 资源上传类型，upload: 直接上传不经过资源库，picker:通过资源库选择资源文件
  limit: propTypes.number.def(undefined), // 数量限制 undefined 不限制
  autoUpload: propTypes.bool.def(true), // 自动上传
  drag: propTypes.bool.def(false), // 拖拽上传
  isShowTip: propTypes.bool.def(true), // 是否显示提示
  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传（默认为 false）
  apiType: propTypes.oneOf<'multiple'>(['multiple']).def(undefined) // 上传文件接口类型
})

const { httpRequest } = useUpload(props.apiType)

// ========== 上传相关 ==========
const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadResourceFile[]>([])
const fileList = ref<UploadResourceFile[]>([])
const uploadNumber = ref<number>(0)
const previewList = ref<string[]>([]) // 图片预览数组
const previewIndex = ref(0) // 预览索引
const isPreview = ref(false) // 是否开启预览
const config = ref<AcceptSizeType>() // 系统资源配置
const mimetypeConfig = ref<MimetypeType>('') // 资源类型
const acceptConfig = ref('') // 允许的资源扩展名
const ResourcePickerRef = ref()

// 监听模型绑定值变动
watch(
  () => props.modelValue,
  (val: string | string[]) => {
    if (!val) {
      fileList.value = [] // fix：处理掉缓存，表单重置后上传组件的内容并没有重置
      return
    }

    fileList.value = [] // 保障数据为空
    previewList.value = []
    // 情况1：字符串
    if (isString(val)) {
      fileList.value.push(
        ...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url, mimetype: getMimetype(url), ext: getFileExt(url) }))
      )
      if (val.indexOf(',') !== -1) {
        const valList = val.split(',')
        valList.forEach(item => {
          if (getMimetype(item) === 'image' || getMimetype(item) === 'video') {
            previewList.value.push(item)
          }
        })
      } else {
        if (getMimetype(val) === 'image' || getMimetype(val) === 'video') {
          previewList.value.push(val)
        }
      }
      return
    }
    // 情况2：数组
    fileList.value.push(
      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url, mimetype: getMimetype(url), ext: getFileExt(url) }))
    )
    val.forEach(item => {
      if (getMimetype(item) === 'image' || getMimetype(item) === 'video') {
        previewList.value.push(item)
      }
    })
  },
  { immediate: true, deep: true }
)

watch(
  () => props.mimetype,
  async (v) => {
    if (!props.accept){
      config.value = await getAcceptSize(v as any)
      mimetypeConfig.value = v
      acceptConfig.value = config.value.accept
    }
  },
  { immediate: true }
)

watch(
  () => props.accept,
  async (v) => {
    if (v) {
      const realConfig = getExtMimetypeConfig(v)
      config.value = await getAcceptSizeByAccept(v)
      mimetypeConfig.value = realConfig.mimetype
      acceptConfig.value = v
    }
  },
  { immediate: true }
)

const size = computed(() => `${props.size}`.replace(/px/, '') + 'px')

// 文件上传之前判断
const beforeUpload: UploadProps['beforeUpload'] = async (file: UploadRawFile) => {
  if (fileList.value.length >= props.limit && props.limit) {
    message.error(t('upload', 'limit', [props.limit]))
    return false
  }
  let fileExtension = getFileExt(file.name)
  let fileMimetype = getMimetype(file.name)
  let fileConfig = config.value
  if (isArray(mimetypeConfig.value) && config.value?.config) {
    fileConfig = config.value?.config[fileMimetype]
  }
  const isAcceptFile = acceptConfig.value.split(',').some((ext: string) => {
    if (ext.indexOf(fileMimetype)) return true
    return !!(fileExtension && ext.indexOf(fileExtension) > -1)
  })
  if (!isAcceptFile) {
    message.error(t('upload', 'accept', [acceptConfig.value, t('upload', fileMimetype)]))
    return false
  }
  const isAllowSize = fileConfig?.size === 0 || file.size < (fileConfig?.size as number)
  if (!isAllowSize) {
    message.error(t('upload', 'size', [fileConfig?.sizeText]))
    return false
  }
  message.success(t('upload', 'waiting'))
  uploadNumber.value++
}

// 文件上传成功
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
  message.success(t('upload', 'success'))
  // 删除自身
  const index = fileList.value.findIndex((item: any) => item.response?.url === res.url)
  fileList.value.splice(index, 1)
  uploadList.value.push({ name: res.name, url: res.url, mimetype: getMimetype(res.url), ext: getFileExt(res.url), duration: res.duration })
  if (getMimetype(res.url) === 'image' || getMimetype(res.url) === 'video') {
    previewList.value.push(res.url)
  }
  if (uploadList.value.length == uploadNumber.value) {
    fileList.value.push(...uploadList.value)
    uploadList.value = []
    uploadNumber.value = 0
    emitUpdateModelValue()
  }
}
// 文件数超出提示
const handleExceed: UploadProps['onExceed'] = (): void => {
  message.error(t('upload', 'limit', [props.limit]))
}
// 上传错误提示
const excelUploadError: UploadProps['onError'] = (): void => {
  message.error(t('upload', 'error'))
}

// 删除
const handleRemove = (index) => {
  fileList.value.splice(index, 1)
  emitUpdateModelValue()
}

// 预览
const handlePreview = (url) => {
  console.log(previewList.value)
  previewIndex.value = previewList.value.findIndex(item => item === url)
  isPreview.value = true
}

// 发送文件链接列表更新
const emitUpdateModelValue = () => {
  // 情况1：数组结果
  let result: string | string[] = fileList.value.map((file) => file.url!)
  // 情况2：逗号分隔的字符串
  if (props.limit === 1 || isString(props.modelValue)) {
    result = result.join(',')
  }
  emit('update:modelValue', result)
  if (props.limit === 1) {
    emit('success', fileList.value[0])
  } else {
    emit('success', fileList.value)
  }
}

const open = () => {
  ResourcePickerRef.value.open(mimetypeConfig.value, props.limit === 1 ? false : true)
}
// 选择资源弹窗确定后，返回已选择的资源
const handleResourcePickerChange = (v) => {
  if (props.limit === 1) {
    fileList.value.push(v)
    if (getMimetype(v.url) === 'image' || getMimetype(v.url) === 'video') {
      previewList.value.push(v.url)
    }
  } else {
    v.forEach(item => {
      fileList.value.push(item)
      if (getMimetype(item.url) === 'image' || getMimetype(item.url) === 'video') {
        previewList.value.push(item.url)
      }

    })
  }
  emitUpdateModelValue()
}
</script>

<template>
  <div :class="[prefixCls, props.limit === 1 ? 'flex-row' : 'flex-col']">
    <div class="source-pane">
      <template v-if="fileList.length > 0">
        <div v-for="item, index in fileList" :key="index" :class="`${prefixCls}-preview`" :title="item.name">
          <div class="mask">
            <SvgIcon v-if="item.mimetype === 'image' || item.mimetype === 'video'" icon="ri:eye-line" size="18" :title="t('action', 'preview')" @click="handlePreview(item.url)" />
            <SvgIcon icon="ri:delete-bin-5-line" size="18" :title="t('action', 'remove')" @click="handleRemove(index)" />
          </div>
          <template v-if="item.mimetype === 'image'">
            <ElImage :src="item.url" fit="fill" />
          </template>
          <SvgIcon v-else-if="item.mimetype === 'video'" icon="ci:filetype-video" size="64" />
          <SvgIcon v-else-if="item.mimetype === 'audio'" icon="ci:filetype-audio" size="64" />
          <template v-else>
            <SvgIcon v-if="item.ext && fileExtList.includes(item.ext)" :icon="'ci:filetype-' + item.ext" size="64" />
            <SvgIcon v-else icon="ci:filetype-unknown" size="64" />
          </template>
        </div>
      </template>
      <ElUpload
        v-if="config && (props.limit !== 1 || props.limit === 1 && fileList.length === 0) && type === 'upload'"
        ref="uploadRef"
        v-model:file-list="fileList"
        :auto-upload="autoUpload"
        :before-upload="beforeUpload"
        :disabled="disabled"
        :drag="drag"
        :http-request="httpRequest"
        :limit="props.limit"
        :multiple="props.limit !== 1"
        :on-error="excelUploadError"
        :on-exceed="handleExceed"
        :on-success="handleFileSuccess"
        :accept="acceptConfig"
        :show-file-list="false"
        :class="`${prefixCls}-new`">
        <SvgIcon icon="ep:plus" size="28" color="" />
      </ElUpload>
      <div v-if="config && (props.limit !== 1 || props.limit === 1 && fileList.length === 0) && type === 'picker'" :class="`${prefixCls}-new`" @click="open">
        <div class="el-upload el-upload--text">
          <SvgIcon icon="ep:plus" size="28" color="" />
        </div>
      </div>
    </div>
    <div v-if="props.isShowTip" :class="`${prefixCls}-tips`">
      <template v-if="props.limit !== 1">
        <p class="tips line" v-html="config?.helpTextLine" />
      </template>
      <p v-else class="tips" v-html="config?.helpText" />
    </div>
    <!-- 弹窗：图片预览 -->
    <MediaViewer v-if="isPreview" :initial-index="previewIndex" :url-list="previewList" :hide-on-click-modal="true" @close="isPreview = false" />
    <!-- 弹窗：资源库选择图片 -->
    <ResourcePicker ref="ResourcePickerRef" @change="handleResourcePickerChange" />
  </div>
</template>

<style scoped lang="scss">
$prefix-cls: #{$namespace}-upload-source;
.#{$prefix-cls} {
  display: flex;
  flex: 1;

  .source-pane{
    display: flex;
    flex-wrap: wrap;
  }

  &-preview {
    position: relative;
    display: flex;
    width: v-bind(size);
    height: v-bind(size);
    margin: 6px;
    overflow: hidden;
    background-color: var(--el-fill-color-lighter);
    border: 1px solid var(--el-border-color-darker);
    border-radius: 6px;
    box-sizing: border-box;
    align-items: center;
    justify-content: center;

    &:hover {
      .mask {
        opacity: 1;
        visibility: visible;
      }
    }

    .mask {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 10;
      display: flex;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.5);
      opacity: 0;
      visibility: hidden;
      transition: all 0.3s;
      align-items: center;
      justify-content: center;

      >div {
        line-height: 0;
      }

      .el-icon {
        margin: 0 6px;
        font-size: 24px !important;
        color: #fff;
        cursor: pointer;
        transition: all 0.3s;

        &:hover {
          font-size: 32px !important;
        }
      }
    }

    .el-image,
    .el-image :deep(.el-image__inner) {
      max-width: calc(v-bind(size) - 2px);
      max-height: calc(v-bind(size) - 2px);
    }
  }

  &-new {
    display: flex;
    width: v-bind(size);
    height: v-bind(size);
    margin: 6px;
    cursor: pointer;
    background-color: var(--el-fill-color-lighter);
    border: 1px dashed var(--el-border-color-darker);
    border-radius: 6px;
    box-sizing: border-box;
    align-items: center;
    justify-content: center;

    &:hover {
      border-color: var(--el-color-primary);
    }

    :deep(.el-upload) {
      flex: 1;
      height: 100%;
    }
  }

  &-tips {
    display: flex;
    flex: 1;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    margin: 0 6px 6px;

    .tips {
      margin: 0;

      &.line{
        display: flex;

        :deep(.el-text+.el-text) {
          margin-left: 6px;
        }
      }

      :deep(.el-text){
        display: flex;
        font-size: 12px;
        line-height: 1.4;
        color: var(--el-text-color-secondary);

        span{
          margin: 0 6px;
          color: var(--el-color-primary);
        }
      }
    }

    p {
      font-size: 12px;
      line-height: 24px;
      color: var(--el-text-color-secondary);
    }
  }
}
</style>
